﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Young3.FMSearch.Core.Entities;
using Young3.FMSearch.Core.Entities.GameVersions;
using Young3.FMSearch.Core.Entities.InGame;
using System.Linq.Expressions;

namespace Young3.FMSearch.Core.Managers
{
    /// <summary>
    /// These methods are going to be weaved inside all the properties
    /// through the things described in AttributeWeaver
    /// </summary>
    internal static class PropertyInvoker
    {
        public static T Get<T>(int offset, ArraySegment<byte> baseObject, int memoryAddress, DatabaseModeEnum databaseMode)
        {
            if (databaseMode == DatabaseModeEnum.Cached)
                return (T)ProcessManager.ReadFromBuffer(baseObject.Array, baseObject.Offset + offset, typeof(T));
            else //realtime
            {
                //this should get thru a same wrapper as above

                int offsetToFind = memoryAddress + offset;

                if (typeof(Int16) == typeof(T))
                    return (T)(object)ProcessManager.ReadInt16(offsetToFind);
                else if (typeof(Byte) == typeof(T))
                    return (T)(object)ProcessManager.ReadByte(offsetToFind);
                else if (typeof(DateTime) == typeof(T))
                    return (T)(object)ProcessManager.ReadDateTime(offsetToFind);
                else if (typeof(Int32) == typeof(T))
                    return (T)(object)ProcessManager.ReadInt32(offsetToFind);
                else if (typeof(SByte) == typeof(T))
                    return (T)(object)ProcessManager.ReadSByte(offsetToFind);
                else if (typeof(Single) == typeof(T))
                    return (T)(object)ProcessManager.ReadSingle(offsetToFind);
                else if (typeof(UInt32) == typeof(T))
                    return (T)(object)ProcessManager.ReadUInt32(offsetToFind);
                else if (typeof(ushort) == typeof(T))
                    return (T)(object)ProcessManager.ReadUShort16(offsetToFind);
                else
                    return default(T);
            }
        }

        public static string GetString(int offset, int additionalStringOffset, ArraySegment<byte> baseObject, int memoryAddress, DatabaseModeEnum databaseMode)
        {
            if (databaseMode == DatabaseModeEnum.Cached)
                return ProcessManager.ReadString(baseObject, offset, additionalStringOffset);
            else //realtime
                return ProcessManager.ReadString(memoryAddress + offset, additionalStringOffset);
        }

        public static void Set<T>(int offset, int memoryAddressObject, T newValue, DatabaseModeEnum databaseMode)
        {
            if (databaseMode == DatabaseModeEnum.Realtime)
            {
                //if we have a 'simple' type; we'll just overwrite the corresponding bytes
                //if not, we have a pointer to overwrite if it is a BaseObject (another ingame object)
                Type instanceType = newValue.GetType();
                int offsetToFind = memoryAddressObject + offset;

                if (typeof(Int16) == instanceType)
                    ProcessManager.WriteInt16((Int16)(object)newValue, offsetToFind);
                else if (typeof(Byte) == instanceType)
                    ProcessManager.WriteByte((byte)(object)newValue, offsetToFind);
                else if (typeof(DateTime) == instanceType)
                    ProcessManager.WriteDateTime((DateTime)(object)newValue, offsetToFind);
                else if (typeof(Int32) == instanceType)
                    ProcessManager.WriteInt32((Int32)(object)newValue, offsetToFind);
                else if (typeof(SByte) == instanceType)
                    ProcessManager.WriteSByte((sbyte)(object)newValue, offsetToFind);

                else if (newValue is BaseObject)
                    ProcessManager.WriteInt32(((BaseObject)(object)newValue).MemoryAddress, offsetToFind);
            }
        }

        private static Dictionary<Type, Func<int,IVersion, object>> pointerDelegateDictionary = new Dictionary<Type, Func<int,IVersion, object>>();
        public static T GetPointer<T>(int offset, ArraySegment<byte> baseObject, int memoryAddress, DatabaseModeEnum databaseMode, IVersion version)
            where T: class
        { 
            //returns a value based on the FMPointer
            if (databaseMode == DatabaseModeEnum.Cached)
            {
                int memAddress = ProcessManager.ReadInt32(baseObject.Array, offset + baseObject.Offset);
                var objectStore = ((Dictionary<int, T>)ObjectManagerWrapper.ObjectManagers[databaseMode].ObjectStore[typeof(T)]);
                if (objectStore.ContainsKey(memAddress))
                    return objectStore[memAddress] as T;
                else return default(T);
            }
            else
            {
                int memAddress = memoryAddress+offset;

                if (typeof(T) == typeof(Contract))
                    memAddress = ProcessManager.ReadInt32(memAddress);


                if(!pointerDelegateDictionary.ContainsKey(typeof(T)))
                {
                    //nieuwe bouwe
                    System.Reflection.ConstructorInfo ci =
                        typeof(T).GetConstructor(new[] { typeof(int), typeof(IVersion) });

                    ParameterExpression memAddressParam = Expression.Parameter(typeof(int), "memAdd");
                    ParameterExpression versionParam = Expression.Parameter(typeof(IVersion), "version");

                    LambdaExpression lambda = Expression.Lambda(
                        Expression.Convert(Expression.New(ci, memAddressParam, versionParam), typeof(object))
                        , memAddressParam, versionParam);
                    
                    lock (pointerDelegateLock)
                    {
                        if (!pointerDelegateDictionary.ContainsKey(typeof(T)))
                        {
                            pointerDelegateDictionary.Add(typeof (T),
                                                          (Func<int, IVersion, object>) lambda.Compile());
                        }
                    }
                }
                return (T)pointerDelegateDictionary[typeof(T)].Invoke(ProcessManager.ReadInt32(memAddress), version);
            }
        }

        private static object pointerDelegateLock = new object();
    }
}
